/**
 * 
 */
package xiaojian.toolkit.bean.tlv.encode.encoders;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import xiaojian.toolkit.bean.bytebean.ByteFieldToDesc;
import xiaojian.toolkit.bean.bytebean.codec.BeanCodecUtil;
import xiaojian.toolkit.bean.bytebean.core.ByteFieldCodec;
import xiaojian.toolkit.bean.bytebean.core.ByteFieldDesc;
import xiaojian.toolkit.bean.bytebean.core.EncContextFactory;
import xiaojian.toolkit.bean.bytebean.core.FieldCodecCategory;
import xiaojian.toolkit.bean.bytebean.core.FieldCodecProvider;
import xiaojian.toolkit.bean.tlv.annotation.TLVAttribute;
import xiaojian.toolkit.bean.tlv.encode.TLVEncodeContext;
import xiaojian.toolkit.bean.tlv.encode.TLVEncodeContextFactory;
import xiaojian.toolkit.bean.tlv.encode.TLVEncoder;
import xiaojian.toolkit.bean.tlv.encode.TLVEncoderOfBean;
import xiaojian.toolkit.bean.tlv.meta.TLVCodecUtils;
import xiaojian.toolkit.util.ByteUtils;
import xiaojian.toolkit.util.FieldUtils;

/**
 * @author hp 在原encoder的基础上增加对List类型字段的支持
 */
public class BeanMixedTLVEncoder implements TLVEncoderOfBean {

	private static final Logger LOG = LoggerFactory
			.getLogger(BeanMixedTLVEncoder.class);

	private TLVEncodeContextFactory tlvEncodeContextFactory;
	private EncContextFactory byteEncContextFactory;
	private FieldCodecProvider byteCodecProvider;

	private final BeanCodecUtil util = new BeanCodecUtil(new ByteFieldToDesc());

	/**
	 * @return the tlvEncodeContextFactory
	 */
	public TLVEncodeContextFactory getTlvEncodeContextFactory() {
		return tlvEncodeContextFactory;
	}

	/**
	 * @param tlvEncodeContextFactory
	 *            the tlvEncodeContextFactory to set
	 */
	public void setTlvEncodeContextFactory(
			TLVEncodeContextFactory tlvEncodeContextFactory) {
		this.tlvEncodeContextFactory = tlvEncodeContextFactory;
	}

	/**
	 * @return the byteEncContextFactory
	 */
	public EncContextFactory getByteEncContextFactory() {
		return byteEncContextFactory;
	}

	/**
	 * @param byteEncContextFactory
	 *            the byteEncContextFactory to set
	 */
	public void setByteEncContextFactory(EncContextFactory byteEncContextFactory) {
		this.byteEncContextFactory = byteEncContextFactory;
	}

	/**
	 * @return the byteCodecProvider
	 */
	public FieldCodecProvider getByteCodecProvider() {
		return byteCodecProvider;
	}

	/**
	 * @param byteCodecProvider
	 *            the byteCodecProvider to set
	 */
	public void setByteCodecProvider(FieldCodecProvider byteCodecProvider) {
		this.byteCodecProvider = byteCodecProvider;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.skymobi.bean.tlv.encode.encoders.TLVEncoderOfBean#getEncodeContextFactory
	 * ()
	 */
	public TLVEncodeContextFactory getEncodeContextFactory() {
		return tlvEncodeContextFactory;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.skymobi.bean.tlv.encode.encoders.TLVEncoderOfBean#encode(java.lang
	 * .Object, com.skymobi.bean.tlv.encode.TLVEncodeContext)
	 */
	public List<byte[]> encode(final Object tlvBean, final TLVEncodeContext ctx) {
		if (null == tlvBean) {
			throw new RuntimeException("null == tlvBean.");
		}

		final List<byte[]> ret = new ArrayList<byte[]>();

		encodeByteFields(tlvBean, ret);

		encodeTLVFields(tlvBean, ctx, ret);

		return ret;
	}

	/**
	 * @param tlvBean
	 * @param ctx
	 * @param ret
	 */
	@SuppressWarnings("unchecked")
	private void encodeTLVFields(final Object tlvBean,
			final TLVEncodeContext ctx, final List<byte[]> ret) {
		final Field[] fields = TLVCodecUtils.getTLVFieldsOf(tlvBean.getClass());

		for (Field field : fields) {
			final TLVAttribute param = field.getAnnotation(TLVAttribute.class);

			if (null == param) {
				// not TLVAttribute
				continue;
			}

			field.setAccessible(true);
			Object src = null;
			try {
				src = field.get(tlvBean);
			} catch (IllegalArgumentException e) {
				LOG.error("transform:", e);
			} catch (IllegalAccessException e) {
				LOG.error("transform:", e);
			}
			if (null == src) {
				continue;
			}

			Class<?> type = param.type();
			if (TLVAttribute.class.equals(type)) {
				type = src.getClass();
			}

			if (List.class.isAssignableFrom(type)) {
				final Class<?> componentType = FieldUtils
						.getComponentClass(field);
				final TLVEncoder encoder = ctx.getEncoderRepository()
						.getEncoderOf(componentType);
				if (null == encoder) {
					LOG.error("field[" + field + "]/"
							+ componentType.getSimpleName()
							+ " can not found encoder, ignore");
					continue;
				}
				final List<Object> list = (List<Object>) src;
				for (Object component : list) {
					final List<byte[]> dest = encoder.encode(component,
							tlvEncodeContextFactory.createEncodeContext(
									componentType, field));
					// tag
					ret.add(ctx.getNumberCodec().int2Bytes(param.tag(), 4));

					// len
					ret.add(ctx.getNumberCodec().int2Bytes(
							ByteUtils.totalByteSizeOf(dest), 4));

					ret.addAll(dest);
				}
			} else {
				final TLVEncoder encoder = ctx.getEncoderRepository()
						.getEncoderOf(type);
				if (null == encoder) {
					LOG.error("field[" + field
							+ "] can not found encoder, ignore");
					continue;
				}
				final List<byte[]> dest = encoder.encode(src,
						tlvEncodeContextFactory
								.createEncodeContext(type, field));

				// tag
				ret.add(ctx.getNumberCodec().int2Bytes(param.tag(), 4));

				// len
				ret.add(ctx.getNumberCodec().int2Bytes(
						ByteUtils.totalByteSizeOf(dest), 4));

				ret.addAll(dest);
			}
		}
	}

	/**
	 * @param tlvBean
	 * @param ret
	 */
	private void encodeByteFields(final Object tlvBean, final List<byte[]> ret) {
		final List<ByteFieldDesc> desces = util.getFieldDesces(tlvBean
				.getClass());
		if (!desces.isEmpty()) {
			// include ByteField annotation
			final ByteFieldCodec anyCodec = byteCodecProvider
					.getCodecOf(FieldCodecCategory.ANY);

			for (ByteFieldDesc desc : desces) {

				final Field field = desc.getField();
				final Class<?> fieldClass = field.getType();

				field.setAccessible(true);
				Object fieldValue = null;

				try {
					fieldValue = field.get(tlvBean);
				} catch (IllegalArgumentException e) {
					LOG.error("BeanMixedTLVEncoder:", e);
				} catch (IllegalAccessException e) {
					LOG.error("BeanMixedTLVEncoder:", e);
				}

				ret.add(anyCodec.encode(byteEncContextFactory.createEncContext(
						fieldValue, fieldClass, desc)));
			}
		}
	}

}
